using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Diagnostics;
using System.Collections;

namespace Intergr8.IO.File
{
    /// <summary>
    /// Maintain data in a DataSet containing 1 or more DataTables.
    /// Data is stored in the DataSet.Tables["DATA"} with header and footer in the DataSet.Tables["HEADER"] and DataSet.Tables["FOOTER"] respectively.
    /// Header and Footer tables are not mandatory
    /// </summary>
    public class DataSetIO : FileIOBase
    {


        public const string HEADERTABLENAME = "HEADER";
        public const string FOOTERTABLENAME = "FOOTER";
        public const string DATATABLENAME = "DATA";

        private DataSet _ds = new DataSet();

        private System.Collections.Generic.List<System.Type> _columnTypes = null;

        /// <summary>
        /// Default constructior. Creates an uninitialised DataSetIO object
        /// </summary>
        public DataSetIO()
            : base()
        {
        }

        /// <summary>
        /// Parameterised DataSetIO constructor. Creates an initialised DataSetIO object pointing to an existing file specified by the fullpath parameter
        /// </summary>
        /// <param name="fullpath">Fully qualified path to and existing file</param>
        public DataSetIO(string fullpath)
            : base(fullpath)
        {
        }

        /// <summary>
        ///  Parameterised DataSetIO constructor. Creates an initialised DataSetIO object pointing to an existing file specified by the path and filename parameters
        /// </summary>
        /// <param name="path">Fully qualified path to an existing folder</param>
        /// <param name="filename">Filename to read</param>
        public DataSetIO(string path, string filename)
            : base(path, filename)
        {
        }

        /// <summary>
        /// Construct a DataSetIO object initialised with the columnTypes array. This array is used to 
        /// set up the column types of the underlying Dataset that will be constructed and returned to the caller.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="filename"></param>
        /// <param name="columnTypes"></param>
        public DataSetIO(string path, string filename, System.Collections.Generic.List<System.Type> columnTypes)
            : base(path, filename)
        {
            _columnTypes = columnTypes;
        }

        /// <summary>
        /// A virtual Read method that performs a default read of the CSV file and returns the data in a DataSet
        /// </summary>
        /// <returns>A DataSet containing the data read from the CSV</returns>
        public virtual DataSet Read()
        {
            Trace.WriteLine("Reading file " + System.IO.Path.Combine(this.WorkingDirectory, this.FileName), "DataSetIO");
            CSVReader reader = null;

            try
            {
                // Attempt to open the File
                reader = new CSVReader(System.IO.Path.Combine(this.WorkingDirectory, this.FileName));

                if (reader == null)
                {
                    throw new Exception("Failed to create CSVReader");
                }

                // For each row in the File, read into a string array
                string[] row = reader.GetCSVLine();
                while (row != null)
                {
                    AddData(row);

                    // Repeat for each row, until an end of file is indicated                    
                    row = reader.GetCSVLine();
                }

            }
            catch (Exception ex)
            {
                Trace.TraceError("Read() failed with exception." + ex.Message);
                throw new Exception("Read() failed with exception. ", ex);
            }
            finally
            {
                reader.Dispose();
            }


            return this.Data;
        }

        /// <summary>
        /// Write the contents of the DataSet.Tables[DATATABLE] to the CSV file
        /// </summary>
        /// <param name="dataset"></param>
        public virtual void Write(DataSet dataset)
        {
            CSVWriter writer = null;
            try
            {
                writer = new CSVWriter(System.IO.Path.Combine(this.WorkingDirectory, this.FileName));

                // Check for a header
                DataTable tblHeader = dataset.Tables[HEADERTABLENAME];
                if (tblHeader != null)
                {
                    // write header
                    WriteTable(writer, tblHeader);
                }

                // check for data
                DataTable tblData = dataset.Tables[DATATABLENAME];
                if (tblData != null)
                {
                    WriteTable(writer, tblData);
                }
                else
                {
                    throw new Exception("DataSetIO.Write expects to find a DATA table");
                }

                // check for a footer
                DataTable tblFooter = dataset.Tables[FOOTERTABLENAME];
                if (tblFooter != null)
                {
                    // write footer
                    WriteTable(writer, tblFooter);
                }

            }
            catch (Exception ex)
            {
                Trace.WriteLine("Write failed with Exception. " + ex.Message, "DataSetIO");
                throw new Exception("Write() failed with exception. ", ex);
            }
            finally
            {
                writer.Dispose();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="tblData"></param>
        private static void WriteTable(CSVWriter writer, DataTable tblData)
        {
            foreach (DataRow row in tblData.Rows)
            {
                object[] rowdata = row.ItemArray;

                writer.PutCSVLine(rowdata);
            }
        }
        

        /// <summary>
        /// Expose ths internal DataSet object
        /// </summary>
        protected DataSet Data
        {
            get { return _ds; }
        }

        /// <summary>
        /// Add a datarow to the DataSet
        /// </summary>
        /// <param name="rowData"></param>
        protected void AddData(string[] rowData)
        {
            Trace.WriteLine("AddData: Adding Data row", "DataSetIO");

            DataTable tblData = null;
            // If we dont already have a Data table, add one
            tblData = _ds.Tables[DATATABLENAME];
            if (tblData == null)
            {
                tblData = _ds.Tables.Add(DATATABLENAME);

                InitialiseColumnTypes(rowData, tblData);
            }

            // Add the data row
            tblData.Rows.Add(rowData);
        }

        /// <summary>
        /// Initialise the column types for a newly created table.
        /// </summary>
        /// <param name="rowData"></param>
        /// <param name="tblData"></param>
        private  void InitialiseColumnTypes(string[] rowData, DataTable tblData)
        {

            // If no specific column type definitions have been applied, use the default of all 'string'
            if (_columnTypes == null)
            {

                // Add the columns based on the current row, 
                // which should be the correct length or we wouldnt have got this far
                foreach (string col in rowData)
                {
                    DataColumn tblCol = tblData.Columns.Add();
                    tblCol.DataType = typeof(String);
                }
            }
            else
            {
                for (int index = 0; index < rowData.Length; index++)
                {
                    DataColumn tblCol = tblData.Columns.Add(index.ToString(),_columnTypes[index]);
                }
            }
        }


        /// <summary>
        /// Add a header table to the Dataset
        /// </summary>
        /// <param name="rowHeader"></param>
        protected void AddHeader(string[] rowHeader)
        {
            Trace.WriteLine("AddData: Adding Header row", "DataSetIO");

            // Check we dont already have a Header Table
            if (_ds.Tables[HEADERTABLENAME] != null)
            {
                throw new Exception("A Header has already been added to this DataSet");
            }

            DataTable tblHeader = _ds.Tables.Add(HEADERTABLENAME);

            // Build up the columns
            foreach (string col in rowHeader)
            {
                DataColumn tblCol = tblHeader.Columns.Add();
                tblCol.DataType = typeof(String);
            }

            // Add the data row
            tblHeader.Rows.Add(rowHeader);

        }

        /// <summary>
        ///  Add a footer table to the dataset
        /// </summary>
        /// <param name="rowFooter"></param>
        protected void AddFooter(string[] rowFooter)
        {
            Trace.WriteLine("AddData: Adding Footer row", "DataSetIO");

            // Check we dont already have a Header Table
            if (_ds.Tables[FOOTERTABLENAME] != null)
            {
                throw new Exception("A Footer has already been added to this DataSet");
            }

            DataTable tblFooter = _ds.Tables.Add(FOOTERTABLENAME);

            // Build up the columns
            foreach (string col in rowFooter)
            {
                DataColumn tblCol = tblFooter.Columns.Add();
                tblCol.DataType = typeof(String);
            }

            // Add the data row
            tblFooter.Rows.Add(rowFooter);
        }
    }
}
